// Copyright Epic Games, Inc. All Rights Reserved.

#include "LyraCameraMode_ThirdPerson.h"
#include "Camera/LyraCameraMode.h"
#include "Components/PrimitiveComponent.h"
#include "Camera/LyraPenetrationAvoidanceFeeler.h"
#include "Curves/CurveVector.h"
#include "Engine/Canvas.h"
#include "GameFramework/CameraBlockingVolume.h"
#include "LyraCameraAssistInterface.h"
#include "GameFramework/Controller.h"
#include "GameFramework/Character.h"
#include "Math/RotationMatrix.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraCameraMode_ThirdPerson)

namespace LyraCameraMode_ThirdPerson_Statics
{
	static const FName NAME_IgnoreCameraCollision = TEXT("IgnoreCameraCollision"); // 忽略相机碰撞的标签名称
}

ULyraCameraMode_ThirdPerson::ULyraCameraMode_ThirdPerson()
{
	TargetOffsetCurve = nullptr; // 初始化目标偏移曲线为空

	// 添加穿透避免触须配置
	PenetrationAvoidanceFeelers.Add(FLyraPenetrationAvoidanceFeeler(FRotator(+00.0f, +00.0f, 0.0f), 1.00f, 1.00f, 14.f, 0));
	PenetrationAvoidanceFeelers.Add(FLyraPenetrationAvoidanceFeeler(FRotator(+00.0f, +16.0f, 0.0f), 0.75f, 0.75f, 00.f, 3));
	PenetrationAvoidanceFeelers.Add(FLyraPenetrationAvoidanceFeeler(FRotator(+00.0f, -16.0f, 0.0f), 0.75f, 0.75f, 00.f, 3));
	PenetrationAvoidanceFeelers.Add(FLyraPenetrationAvoidanceFeeler(FRotator(+00.0f, +32.0f, 0.0f), 0.50f, 0.50f, 00.f, 5));
	PenetrationAvoidanceFeelers.Add(FLyraPenetrationAvoidanceFeeler(FRotator(+00.0f, -32.0f, 0.0f), 0.50f, 0.50f, 00.f, 5));
	PenetrationAvoidanceFeelers.Add(FLyraPenetrationAvoidanceFeeler(FRotator(+20.0f, +00.0f, 0.0f), 1.00f, 1.00f, 00.f, 4));
	PenetrationAvoidanceFeelers.Add(FLyraPenetrationAvoidanceFeeler(FRotator(-20.0f, +00.0f, 0.0f), 0.50f, 0.50f, 00.f, 4));
}

void ULyraCameraMode_ThirdPerson::UpdateView(float DeltaTime)
{
	UpdateForTarget(DeltaTime); // 更新目标
	UpdateCrouchOffset(DeltaTime); // 更新下蹲偏移

	FVector PivotLocation = GetPivotLocation() + CurrentCrouchOffset; // 计算枢轴位置（包含下蹲偏移）
	FRotator PivotRotation = GetPivotRotation(); // 获取枢轴旋转

	PivotRotation.Pitch = FMath::ClampAngle(PivotRotation.Pitch, ViewPitchMin, ViewPitchMax); // 限制俯仰角范围

	View.Location = PivotLocation; // 设置视图位置
	View.Rotation = PivotRotation; // 设置视图旋转
	View.ControlRotation = View.Rotation; // 设置控制旋转
	View.FieldOfView = FieldOfView; // 设置视图视野

	// 使用俯仰角应用第三人称偏移。
	if (!bUseRuntimeFloatCurves) // 如果不使用运行时浮点曲线
	{
		if (TargetOffsetCurve) // 如果目标偏移曲线有效
		{
			const FVector TargetOffset = TargetOffsetCurve->GetVectorValue(PivotRotation.Pitch); // 从曲线获取目标偏移
			View.Location = PivotLocation + PivotRotation.RotateVector(TargetOffset); // 计算视图位置
		}
	}
	else // 如果使用运行时浮点曲线
	{
		FVector TargetOffset(0.0f); // 初始化目标偏移

		TargetOffset.X = TargetOffsetX.GetRichCurveConst()->Eval(PivotRotation.Pitch); // 评估X偏移曲线
		TargetOffset.Y = TargetOffsetY.GetRichCurveConst()->Eval(PivotRotation.Pitch); // 评估Y偏移曲线
		TargetOffset.Z = TargetOffsetZ.GetRichCurveConst()->Eval(PivotRotation.Pitch); // 评估Z偏移曲线

		View.Location = PivotLocation + PivotRotation.RotateVector(TargetOffset); // 计算视图位置
	}

	// 调整最终期望的相机位置以防止任何穿透
	UpdatePreventPenetration(DeltaTime); // 更新防止穿透
}

void ULyraCameraMode_ThirdPerson::UpdateForTarget(float DeltaTime)
{

	if (const ACharacter* TargetCharacter = Cast<ACharacter>(GetTargetActor())) // 如果目标Actor是角色
	{
		if (TargetCharacter->IsCrouched()) // 如果角色处于下蹲状态
		{
			const ACharacter* TargetCharacterCDO = TargetCharacter->GetClass()->GetDefaultObject<ACharacter>(); // 获取角色类默认对象
			const float CrouchedHeightAdjustment = TargetCharacterCDO->CrouchedEyeHeight - TargetCharacterCDO->BaseEyeHeight; // 计算下蹲高度调整

			SetTargetCrouchOffset(FVector(0.f, 0.f, CrouchedHeightAdjustment)); // 设置目标下蹲偏移

			return;
		}
	}

	SetTargetCrouchOffset(FVector::ZeroVector); // 否则设置目标下蹲偏移为零向量
}

void ULyraCameraMode_ThirdPerson::DrawDebug(UCanvas* Canvas) const
{
	Super::DrawDebug(Canvas); // 调用父类的绘制调试

#if ENABLE_DRAW_DEBUG
	FDisplayDebugManager& DisplayDebugManager = Canvas->DisplayDebugManager; // 获取调试显示管理器
	for (int i = 0; i < DebugActorsHitDuringCameraPenetration.Num(); i++) // 遍历穿透期间击中的Actor
	{
		DisplayDebugManager.DrawString( // 绘制字符串
			FString::Printf(TEXT("HitActorDuringPenetration[%d]: %s")
				, i
				, *DebugActorsHitDuringCameraPenetration[i]->GetName())); // 显示击中的Actor名称
	}

	LastDrawDebugTime = GetWorld()->GetTimeSeconds(); // 更新最后绘制调试时间
#endif
}

void ULyraCameraMode_ThirdPerson::UpdatePreventPenetration(float DeltaTime)
{
	if (!bPreventPenetration) // 如果不防止穿透
	{
		return; // 直接返回
	}

	AActor* TargetActor = GetTargetActor(); // 获取目标Actor

	APawn* TargetPawn = Cast<APawn>(TargetActor); // 转换为Pawn
	AController* TargetController = TargetPawn ? TargetPawn->GetController() : nullptr; // 获取控制器
	ILyraCameraAssistInterface* TargetControllerAssist = Cast<ILyraCameraAssistInterface>(TargetController); // 转换为相机辅助接口

	ILyraCameraAssistInterface* TargetActorAssist = Cast<ILyraCameraAssistInterface>(TargetActor); // 转换为相机辅助接口

	TOptional<AActor*> OptionalPPTarget = TargetActorAssist ? TargetActorAssist->GetCameraPreventPenetrationTarget() : TOptional<AActor*>(); // 获取防止穿透目标
	AActor* PPActor = OptionalPPTarget.IsSet() ? OptionalPPTarget.GetValue() : TargetActor; // 设置防止穿透Actor
	ILyraCameraAssistInterface* PPActorAssist = OptionalPPTarget.IsSet() ? Cast<ILyraCameraAssistInterface>(PPActor) : nullptr; // 转换为相机辅助接口

	const UPrimitiveComponent* PPActorRootComponent = Cast<UPrimitiveComponent>(PPActor->GetRootComponent()); // 获取根组件
	if (PPActorRootComponent) // 如果根组件有效
	{
		// 尝试自动选择安全位置，以便在瞄准时减少相机平移。
		// 我们的相机是我们的十字线，所以我们希望保持瞄准尽可能稳定和平滑。
		// 选择胶囊体上最接近我们瞄准线的点。
		FVector ClosestPointOnLineToCapsuleCenter; // 声明线上最接近胶囊体中心的点
		FVector SafeLocation = PPActor->GetActorLocation(); // 安全位置初始化为Actor位置
		FMath::PointDistToLine(SafeLocation, View.Rotation.Vector(), View.Location, ClosestPointOnLineToCapsuleCenter); // 计算点到线的距离

		// 调整安全距离高度与瞄准线相同，但在胶囊体内。
		float const PushInDistance = PenetrationAvoidanceFeelers[0].Extent + CollisionPushOutDistance; // 计算推入距离
		float const MaxHalfHeight = PPActor->GetSimpleCollisionHalfHeight() - PushInDistance; // 计算最大半高
		SafeLocation.Z = FMath::Clamp(ClosestPointOnLineToCapsuleCenter.Z, SafeLocation.Z - MaxHalfHeight, SafeLocation.Z + MaxHalfHeight); // 限制安全位置Z坐标

		float DistanceSqr; // 距离平方
		PPActorRootComponent->GetSquaredDistanceToCollision(ClosestPointOnLineToCapsuleCenter, DistanceSqr, SafeLocation); // 获取到碰撞的平方距离
		// 推回胶囊体内部，以避免在进行线条检查时初始穿透。
		if (PenetrationAvoidanceFeelers.Num() > 0) // 如果有穿透避免触须
		{
			SafeLocation += (SafeLocation - ClosestPointOnLineToCapsuleCenter).GetSafeNormal() * PushInDistance; // 调整安全位置
		}

		// 然后瞄准线到期望的相机位置
		bool const bSingleRayPenetrationCheck = !bDoPredictiveAvoidance; // 是否单射线穿透检查
		PreventCameraPenetration(*PPActor, SafeLocation, View.Location, DeltaTime, AimLineToDesiredPosBlockedPct, bSingleRayPenetrationCheck); // 防止相机穿透

		ILyraCameraAssistInterface* AssistArray[] = { TargetControllerAssist, TargetActorAssist, PPActorAssist }; // 辅助接口数组

		if (AimLineToDesiredPosBlockedPct < ReportPenetrationPercent) // 如果阻塞百分比小于报告穿透百分比
		{
			for (ILyraCameraAssistInterface* Assist : AssistArray) // 遍历辅助接口
			{
				if (Assist) // 如果辅助接口有效
				{
					// 相机太近，通知辅助接口
					Assist->OnCameraPenetratingTarget(); // 调用相机穿透目标函数
				}
			}
		}
	}
}

void ULyraCameraMode_ThirdPerson::PreventCameraPenetration(class AActor const& ViewTarget, FVector const& SafeLoc, FVector& CameraLoc, float const& DeltaTime, float& DistBlockedPct, bool bSingleRayOnly)
{
#if ENABLE_DRAW_DEBUG
	DebugActorsHitDuringCameraPenetration.Reset(); // 重置调试Actor数组
#endif

	float HardBlockedPct = DistBlockedPct; // 硬阻塞百分比
	float SoftBlockedPct = DistBlockedPct; // 软阻塞百分比

	FVector BaseRay = CameraLoc - SafeLoc; // 基础射线向量
	FRotationMatrix BaseRayMatrix(BaseRay.Rotation()); // 基础射线旋转矩阵
	FVector BaseRayLocalUp, BaseRayLocalFwd, BaseRayLocalRight; // 声明局部坐标轴

	BaseRayMatrix.GetScaledAxes(BaseRayLocalFwd, BaseRayLocalRight, BaseRayLocalUp); // 获取缩放坐标轴

	float DistBlockedPctThisFrame = 1.f; // 本帧阻塞百分比

	int32 const NumRaysToShoot = bSingleRayOnly ? FMath::Min(1, PenetrationAvoidanceFeelers.Num()) : PenetrationAvoidanceFeelers.Num(); // 计算要发射的射线数量
	FCollisionQueryParams SphereParams(SCENE_QUERY_STAT(CameraPen), false, nullptr/*PlayerCamera*/); // 碰撞查询参数

	SphereParams.AddIgnoredActor(&ViewTarget); // 忽略视角目标

	//TODO ILyraCameraTarget.GetIgnoredActorsForCameraPentration();
	//if (IgnoreActorForCameraPenetration)
	//{
	//	SphereParams.AddIgnoredActor(IgnoreActorForCameraPenetration);
	//}

	FCollisionShape SphereShape = FCollisionShape::MakeSphere(0.f); // 创建球形碰撞形状
	UWorld* World = GetWorld(); // 获取世界

	for (int32 RayIdx = 0; RayIdx < NumRaysToShoot; ++RayIdx) // 遍历射线
	{
		FLyraPenetrationAvoidanceFeeler& Feeler = PenetrationAvoidanceFeelers[RayIdx]; // 获取触须
		if (Feeler.FramesUntilNextTrace <= 0) // 如果可以进行追踪
		{
			// 计算射线目标
			FVector RayTarget; // 声明射线目标
			{
				FVector RotatedRay = BaseRay.RotateAngleAxis(Feeler.AdjustmentRot.Yaw, BaseRayLocalUp); // 绕局部上轴旋转
				RotatedRay = RotatedRay.RotateAngleAxis(Feeler.AdjustmentRot.Pitch, BaseRayLocalRight); // 绕局部右轴旋转
				RayTarget = SafeLoc + RotatedRay; // 计算射线目标位置
			}

			// 分别对世界和Pawn命中进行投射。这样我们可以安全地忽略相机的目标Pawn
			SphereShape.Sphere.Radius = Feeler.Extent; // 设置球体半径
			ECollisionChannel TraceChannel = ECC_Camera;		//(Feeler.PawnWeight > 0.f) ? ECC_Pawn : ECC_Camera; // 设置追踪通道

			// 执行多线条检查，确保我们丢弃的命中不会掩盖后面的真实命中（这些是重要的射线）。

			// MT-> 传递相机作为Actor，以便CameraBlockingVolumes知道是相机在进行追踪
			FHitResult Hit; // 命中结果
			const bool bHit = World->SweepSingleByChannel(Hit, SafeLoc, RayTarget, FQuat::Identity, TraceChannel, SphereShape, SphereParams); // 单次球形扫描
#if ENABLE_DRAW_DEBUG
			if (World->TimeSince(LastDrawDebugTime) < 1.f) // 如果距离上次绘制调试时间小于1秒
			{
				DrawDebugSphere(World, SafeLoc, SphereShape.Sphere.Radius, 8, FColor::Red); // 绘制起始球体
				DrawDebugSphere(World, bHit ? Hit.Location : RayTarget, SphereShape.Sphere.Radius, 8, FColor::Red); // 绘制目标球体
				DrawDebugLine(World, SafeLoc, bHit ? Hit.Location : RayTarget, FColor::Red); // 绘制射线
			}
#endif // ENABLE_DRAW_DEBUG

			Feeler.FramesUntilNextTrace = Feeler.TraceInterval; // 设置下一帧追踪间隔

			const AActor* HitActor = Hit.GetActor(); // 获取命中的Actor

			if (bHit && HitActor) // 如果命中且Actor有效
			{
				bool bIgnoreHit = false; // 忽略命中标志

				if (HitActor->ActorHasTag(LyraCameraMode_ThirdPerson_Statics::NAME_IgnoreCameraCollision)) // 如果Actor有忽略相机碰撞标签
				{
					bIgnoreHit = true; // 设置忽略命中
					SphereParams.AddIgnoredActor(HitActor); // 添加忽略的Actor
				}

				// 忽略发生在视角目标前方的CameraBlockingVolume命中。
				if (!bIgnoreHit && HitActor->IsA<ACameraBlockingVolume>()) // 如果是CameraBlockingVolume且不忽略
				{
					const FVector ViewTargetForwardXY = ViewTarget.GetActorForwardVector().GetSafeNormal2D(); // 获取视角目标前方向量（2D）
					const FVector ViewTargetLocation = ViewTarget.GetActorLocation(); // 获取视角目标位置
					const FVector HitOffset = Hit.Location - ViewTargetLocation; // 计算命中偏移
					const FVector HitDirectionXY = HitOffset.GetSafeNormal2D(); // 获取命中方向（2D）
					const float DotHitDirection = FVector::DotProduct(ViewTargetForwardXY, HitDirectionXY); // 计算点积
					if (DotHitDirection > 0.0f) // 如果点积大于0（在前方）
					{
						bIgnoreHit = true; // 设置忽略命中
						// 在剩余的扫描中忽略此CameraBlockingVolume。
						SphereParams.AddIgnoredActor(HitActor); // 添加忽略的Actor
					}
					else
					{
#if ENABLE_DRAW_DEBUG
						DebugActorsHitDuringCameraPenetration.AddUnique(TObjectPtr<const AActor>(HitActor)); // 添加到调试Actor数组
#endif
					}
				}
				
				if (!bIgnoreHit) // 如果不忽略命中
				{
					float const Weight = Cast<APawn>(Hit.GetActor()) ? Feeler.PawnWeight : Feeler.WorldWeight; // 根据命中Actor类型选择权重
					float NewBlockPct = Hit.Time; // 新的阻塞百分比为命中时间
					NewBlockPct += (1.f - NewBlockPct) * (1.f - Weight); // 调整阻塞百分比

					// 考虑推出距离重新计算阻塞百分比。
					NewBlockPct = ((Hit.Location - SafeLoc).Size() - CollisionPushOutDistance) / (RayTarget - SafeLoc).Size(); // 计算新的阻塞百分比
					DistBlockedPctThisFrame = FMath::Min(NewBlockPct, DistBlockedPctThisFrame); // 取最小阻塞百分比

					// 此触须获得命中，因此下一帧再次追踪
					Feeler.FramesUntilNextTrace = 0; // 重置下一帧追踪间隔

#if ENABLE_DRAW_DEBUG
					DebugActorsHitDuringCameraPenetration.AddUnique(TObjectPtr<const AActor>(HitActor)); // 添加到调试Actor数组
#endif
				}
			}

			if (RayIdx == 0) // 如果是第一条射线（主射线）
			{
				// 不向这个插值，直接捕捉到它
				// 假设射线0是中心/主射线 
				HardBlockedPct = DistBlockedPctThisFrame; // 设置硬阻塞百分比
			}
			else
			{
				SoftBlockedPct = DistBlockedPctThisFrame; // 设置软阻塞百分比
			}
		}
		else
		{
			--Feeler.FramesUntilNextTrace; // 减少下一帧追踪计数
		}
	}

	if (bResetInterpolation) // 如果重置插值
	{
		DistBlockedPct = DistBlockedPctThisFrame; // 直接设置阻塞百分比
	}
	else if (DistBlockedPct < DistBlockedPctThisFrame) // 如果当前阻塞百分比小于本帧阻塞百分比
	{
		// 平滑地向外插值
		if (PenetrationBlendOutTime > DeltaTime) // 如果穿透混合退出时间大于DeltaTime
		{
			DistBlockedPct = DistBlockedPct + DeltaTime / PenetrationBlendOutTime * (DistBlockedPctThisFrame - DistBlockedPct); // 线性插值
		}
		else
		{
			DistBlockedPct = DistBlockedPctThisFrame; // 直接设置
		}
	}
	else
	{
		if (DistBlockedPct > HardBlockedPct) // 如果当前阻塞百分比大于硬阻塞百分比
		{
			DistBlockedPct = HardBlockedPct; // 设置为硬阻塞百分比
		}
		else if (DistBlockedPct > SoftBlockedPct) // 如果当前阻塞百分比大于软阻塞百分比
		{
			// 平滑地向内插值
			if (PenetrationBlendInTime > DeltaTime) // 如果穿透混合进入时间大于DeltaTime
			{
				DistBlockedPct = DistBlockedPct - DeltaTime / PenetrationBlendInTime * (DistBlockedPct - SoftBlockedPct); // 线性插值
			}
			else
			{
				DistBlockedPct = SoftBlockedPct; // 直接设置
			}
		}
	}

	DistBlockedPct = FMath::Clamp<float>(DistBlockedPct, 0.f, 1.f); // 限制阻塞百分比在0到1之间
	if (DistBlockedPct < (1.f - ZERO_ANIMWEIGHT_THRESH)) // 如果阻塞百分比小于阈值
	{
		CameraLoc = SafeLoc + (CameraLoc - SafeLoc) * DistBlockedPct; // 调整相机位置
	}
}

void ULyraCameraMode_ThirdPerson::SetTargetCrouchOffset(FVector NewTargetOffset)
{
	CrouchOffsetBlendPct = 0.0f; // 重置混合百分比
	InitialCrouchOffset = CurrentCrouchOffset; // 设置初始下蹲偏移为当前值
	TargetCrouchOffset = NewTargetOffset; // 设置目标下蹲偏移
}


void ULyraCameraMode_ThirdPerson::UpdateCrouchOffset(float DeltaTime)
{
	if (CrouchOffsetBlendPct < 1.0f) // 如果混合百分比小于1
	{
		CrouchOffsetBlendPct = FMath::Min(CrouchOffsetBlendPct + DeltaTime * CrouchOffsetBlendMultiplier, 1.0f); // 增加混合百分比
		CurrentCrouchOffset = FMath::InterpEaseInOut(InitialCrouchOffset, TargetCrouchOffset, CrouchOffsetBlendPct, 1.0f); // 缓入缓出插值
	}
	else
	{
		CurrentCrouchOffset = TargetCrouchOffset; // 直接设置当前下蹲偏移
		CrouchOffsetBlendPct = 1.0f; // 设置混合百分比为1
	}
}